// ----------------------------------------------------------------------------
// binary_writer.cpp
// ----------------------------------------------------------------------------
#include <fw/base/runtime/binary_writer.h>
#include <fw/base/core/assertion.h>
#include <vector>
#include <map>
#include <string.h>

namespace fw
{
	namespace
	{
		bool ShouldReverse(Endianess e)
		{
			return (e==kBigEndian) ? true : false; // PCはLittleEndian. 
		}
		
		uint32_t SwapEndianess(const uint32_t word)
		{
			return (((word & 0x000000FF) << 24) | ((word & 0x0000FF00) << 8) | ((word & 0x00FF0000) >> 8) | ((word & 0xFF000000) >> 24));
		}
		
		int32_t SwapEndianess(const int32_t word)
		{
			uint32_t temp = static_cast<uint32_t>(word);
			temp = SwapEndianess(temp);
			return static_cast<int32_t>(temp);
		}
		
		uint64_t SwapEndianess(const uint64_t word)
		{
			return (
					( ( word & 0x00000000000000FFULL ) << 56 ) |
					( ( word & 0x000000000000FF00ULL ) << 40 ) |
					( ( word & 0x0000000000FF0000ULL ) << 24 ) |
					( ( word & 0x00000000FF000000ULL ) << 8  ) |
					( ( word & 0x000000FF00000000ULL ) >> 8  ) |
					( ( word & 0x0000FF0000000000ULL ) >> 24 ) |
					( ( word & 0x00FF000000000000ULL ) >> 40 ) |
					( ( word & 0xFF00000000000000ULL ) >> 56 )
					);
		}
		
		int64_t SwapEndianess(const int64_t word)
		{
			uint64_t temp = static_cast<uint64_t>(word);
			temp = SwapEndianess(temp);
			return static_cast<int64_t>(temp);
		}
		
		std::ostream::pos_type StreamPos(std::ostream& out)
		{
			const std::ostream::pos_type invalidatePos(-1);
			std::ostream::pos_type pos = out.tellp();
			return (invalidatePos == pos) ? std::ostream::pos_type(0) : pos;
		}
		
		void WriteAlignmentPad(std::ostream& out, const int alignment)
		{
			const std::ostream::pos_type alignmentPosition(alignment);
			const unsigned char padding = 0;
			while(StreamPos(out) % alignmentPosition)
			{
				out.put(padding);
			}
		}
		
	} // unnamed namespace 
	
	static std::map<size_t, size_t>*	s_offsetTable = 0;
	static BinaryWriter::Label			s_latestLabel = 0x0f;
	
	//--------------------------------------------------------------------
	//! データノードの基底クラス. 
	class Writable
	{
	public:
		enum Type
		{
			kChildNode,
			kDataNode,
			kPreswappedDataNode,
			kAlignNode,
			kLabelNode,
			kOffsetNode,
		}; // enum Type 
		
		Writable(Type nodeType)
		: m_type(nodeType)
		{
		}
		
		virtual ~Writable()
		{
		}
		
		Type				GetType()
		{
			return m_type;
		}
		
		void				SetType(Type nodeType)
		{
			m_type = nodeType;
		}
		
		virtual size_t		GetDataSize(size_t offset) const=0;
		virtual void		WriteData(std::ostream& out, Endianess endianess) const=0;
		virtual void		WriteTag (std::ostream& out, Endianess endianess, OffsetOrigin origin) const
		{ // なにも実装しない.
		}
		
	private:
		Type				m_type;
	}; // class Writable 
		
	//--------------------------------------------------------------------
	// バイナリライタの実装. 
	class BinaryWriterImpl
	{
	private:
		friend class Writable;
		friend class ChildNode;
		friend class DataNode;
		friend class PreswappedDataNode;
		friend class AlignNode;		
		
		enum ChunkType
		{
			kNone,
			kData,
			kContainer,
		}; // enum ChunkType 
		
		ChunkType						m_type;
		unsigned int					m_iffTag;
		OffsetOrigin					m_tagOffset;
		
		std::vector<Writable*>			m_contents;
		
		unsigned int					m_startAlignment;
		mutable size_t					m_dataPosInFile;
		mutable size_t					m_dataSizeInFile;
		
	public:
		BinaryWriterImpl()
		: m_type			(kNone)
		, m_iffTag			(0)
		, m_tagOffset		(kTopOfFile)
		, m_startAlignment	(1)
		{
		}
		
		~BinaryWriterImpl()
		{
			for(std::vector<Writable*>::iterator itr=m_contents.begin();
				itr!=m_contents.end();
				++itr)
			{
				delete (*itr);
			}
		}
		
		size_t							GetDataSize(size_t offset) const
		{
			size_t result=0;
			
			for(std::vector<Writable*>::const_iterator itr=m_contents.begin(); itr!=m_contents.end(); ++itr)
			{
				size_t elementSize = (*itr)->GetDataSize(offset);
				result += elementSize;
				offset += elementSize;
			}
			
			return result;
		}
		
		void							WriteData(std::ostream& out, Endianess endianess) const
		{
		}
		
		void							WriteTag (std::ostream& out, Endianess endianess, OffsetOrigin origin) const
		{
		}
		
	}; // class BinaryWriterImpl 
	
	//--------------------------------------------------------------------
	// 子供を所有するノードクラス.
	class ChildNode : public Writable
	{
	public:
				 ChildNode(BinaryWriterImpl* child)
		: Writable(kChildNode)
		, m_child(child)
		{
		}
		
		virtual ~ChildNode()
		{
			FW_ASSERT(m_child);
			delete m_child;
		}
		
		virtual size_t			GetDataSize(size_t offset) const
		{
			return m_child->GetDataSize(offset);
		}
		
		virtual void			WriteData(std::ostream& out, Endianess endianess) const
		{
			m_child->WriteData(out, endianess);
		}
		
		virtual void			WriteTag (std::ostream& out, Endianess endianess, OffsetOrigin origin) const
		{
			m_child->WriteTag(out, endianess, origin);
		}
		
		BinaryWriterImpl*		GetChild()
		{
			return m_child;
		}
		
		const BinaryWriterImpl*	GetChild() const
		{
			return m_child;
		}
		
	private:
		BinaryWriterImpl*		m_child;
	}; // class ChildNode 

	// データを所有するノードクラス.
	class DataNode : public Writable
	{
	public:
		DataNode()
		: Writable(kDataNode)
		{
		}
		
		virtual size_t		GetDataSize(size_t offset) const
		{
			FW_UNUSED(offset);
			return m_data.size();
		}
		
		virtual void		WriteData(std::ostream& out, Endianess endianess) const
		{
			if(m_data.empty())
			{
				return;
			}
			// 必要に応じてバイトオーダーを置き換える. 
			// この処理は時間がかかるので、必要に応じてあらかじめバイトオーダー変換済みのデータを扱ってください. 
			if(ShouldReverse(endianess))
			{
				const unsigned char* data = &m_data[0];
				size_t numChunks = m_elements.size();
				for(size_t i=0; i<numChunks; ++i)
				{
					unsigned int numElements = m_elements[i].m_num;
					unsigned int elementSize = m_elements[i].m_size;
					unsigned char buffer[16];
					for(unsigned int j=0; j<numElements; ++j)
					{
						for(int k=elementSize; k>0; --k)
						{
							buffer[k-1] = *data; data++;
						}
						out.write(reinterpret_cast<const char*>(buffer), elementSize);
					}
				}
			}
			else
			{
				out.write(reinterpret_cast<const char*>(&m_data[0]), m_data.size());
			}
		}
		
		void			AppendData(const void* data, const unsigned int elementSize, const unsigned int numElements)
		{
			if(elementSize>16)
			{
				FW_PRINTF("BinaryWriter cannot handle data element sizes greater than 16 bytes.");
				return;
			}
			// データを内部ストレージに格納する. 
			const std::vector<unsigned char>::size_type previousSize = m_data.size();
			const std::vector<unsigned char>::size_type sizeChanged  = elementSize * numElements;
			const std::vector<unsigned char>::size_type newSize      = previousSize + sizeChanged;
			m_data.resize(newSize);
			memcpy(&m_data[previousSize], data, sizeChanged);
			
			// データ情報を追加する. 
			Element e;
			e.m_size = elementSize;
			e.m_num  = numElements;
			m_elements.push_back(e);
		}
		
	private:
		struct Element
		{
			unsigned int	m_size;
			unsigned int	m_num;
		}; // struct Element
		
		std::vector<unsigned char>	m_data;
		std::vector<Element>		m_elements;
		
	}; // class DataNode 
	
	class PreswappedDataNode : public Writable
	{
	public:
		 PreswappedDataNode(const void* littleEndian, const void* bigEndian, unsigned int size)
		: Writable(kPreswappedDataNode)
		, m_littleEndian(0)
		, m_bigEndian   (0)
		, m_size		(size)
		{
			if(littleEndian)
			{
				m_littleEndian = new unsigned char[size];
				memcpy(m_littleEndian, littleEndian, size);
			}
			else
			{
				m_littleEndian = 0;
			}
			if(bigEndian)
			{
				m_bigEndian = new unsigned char[size];
				memcpy(m_bigEndian, bigEndian, size);
			}
			else
			{
				m_bigEndian = 0;
			}
		}
		
		~PreswappedDataNode()
		{
			if(m_littleEndian){ delete [] m_littleEndian; }
			if(m_bigEndian)   { delete [] m_bigEndian;    }
		}
		
		virtual void		WriteData(std::ostream& out, Endianess endianess) const
		{
			const char* data = 0;
			if(endianess == kBigEndian)
			{
				FW_ASSERT(m_bigEndian);
				data = reinterpret_cast<const char*>(m_bigEndian);
			}
			else
			{
				FW_ASSERT(m_littleEndian);
				data = reinterpret_cast<const char*>(m_littleEndian);
			}
			out.write(data, m_size);
		}
		
		virtual size_t		GetDataSize(size_t offset) const
		{
			FW_UNUSED(offset);
			return m_size;
		}
		
	private:
		
		unsigned char*		m_littleEndian;
		unsigned char*		m_bigEndian;
		unsigned int		m_size;
		
	}; // class PreswappedDataNode 
	
	class AlignNode : public Writable
	{
	public:
		 AlignNode(unsigned int alignment)
		: Writable(kAlignNode)
		, m_alignment(alignment)
		{
		}
		
		virtual void		WriteData(std::ostream& out, Endianess endianess) const
		{
			WriteAlignmentPad(out, m_alignment);
		}
		
		virtual size_t		GetDataSize(size_t offset) const
		{
			size_t modulo = offset % m_alignment;
			size_t result = modulo ? m_alignment - modulo : 0;
			return result;
		}
	private:
		unsigned int		m_alignment;
		
	}; // class AlignNode 

	class LabelNode : public Writable
	{
	public:
		LabelNode(BinaryWriter::Label label)
		: Writable(kLabelNode)
		, m_label(label)
		{
		}
		
		virtual void		WriteData(std::ostream& out, Endianess endianess) const
		{
			// Label should'nt be used twice. 
			if(s_offsetTable)
			{
				if(s_offsetTable->find(m_label) != s_offsetTable->end())
				{
					FW_PRINTF("Label was used twice.\n");
					FW_BREAK();
					return;
				}
			}
			
			// 
			if(s_offsetTable == 0)
			{
				s_offsetTable = new std::map<size_t, size_t>;
				FW_ASSERT(s_offsetTable);
			}
			size_t labelPosition = StreamPos(out);
			(*s_offsetTable)[m_label] = labelPosition;
		}
		
		virtual size_t		GetDataSize(size_t offset) const
		{
			FW_UNUSED(offset);
			return 0; // ラベル自体はデータサイズを持たない. 
		}
		
		static bool			LabelWasWritten(BinaryWriter::Label label)
		{
			if(s_offsetTable == 0)
			{
				return false;
			}
			return (s_offsetTable->find(label) != s_offsetTable->end());
		}
		
		static size_t		GetOffset(BinaryWriter::Label label)
		{
			if(s_offsetTable == 0)
			{
				return 0;
			}
			if(s_offsetTable->find(label) == s_offsetTable->end())
			{
				return 0; // unused label 
			}
			return s_offsetTable->find(label)->second;
		}
		
		static void			ClearOffsets()
		{
			FW_DELETE(s_offsetTable);
		}
		
	private:
		BinaryWriter::Label	m_label;
	}; // class LabelNode 
	
	class OffsetNode : public Writable
	{
	public:
		OffsetNode(BinaryWriter::Label label, OffsetOrigin origin, unsigned int byteCount, unsigned int stride)
		: Writable(kOffsetNode)
		, m_label(label)
		, m_offsetPosition(0)
		, m_offsetOriginMethod(origin)
		, m_offsetByteSize(byteCount)
		, m_offsetStride(stride)
		{
			if(m_offsetByteSize<1)
			{
				FW_PRINTF("Offset storage must be at least one byte.\n");
				FW_BREAK();
			}
			else if(m_offsetByteSize>4)
			{
				FW_PRINTF("Offset storage of more than 4 bytes are not supported.\n");
				FW_BREAK();
			}
			else if(m_offsetStride<1)
			{
				FW_PRINTF("Offset strides must be at least 1 byte.\n");
				FW_BREAK();
			}
		}
		
		virtual void		WriteData(std::ostream& out, Endianess endianess) const
		{
			m_offsetPosition = StreamPos(out);
			
			unsigned char nullOffset = 0;
			switch (m_offsetOriginMethod)
			{
			case kTopOfFile:
				{
					nullOffset = 0xff;
				}
				break;
			case kOffsetAddress:
				{
					nullOffset = 0;
				}
				break;
			default:
				{
					FW_PRINTF("Unknown offset base type.\n");
				}
				break;
			}
			for(unsigned int byteIndex=0; byteIndex<m_offsetByteSize; ++byteIndex)
			{
				out.write(reinterpret_cast<const char*>(&nullOffset), sizeof(unsigned char));
			}
		}
		
		virtual size_t		GetDataSize(size_t offset) const
		{
			FW_UNUSED(offset);
			return m_offsetByteSize;
		}
		
		void				PatchOffsets(std::ostream& out, Endianess endianess) const
		{
			// ラベルが使われていない場合、オフセットの処理をしない. 
			if(!LabelNode::LabelWasWritten(m_label))
			{
				return;
			}
			// オフセットの位置を求める. 
			int64_t labelPosition = LabelNode::GetOffset(m_label);
			
			// ラベルが正しいアラインメントに配置されているか確認する. 
			if(labelPosition % m_offsetStride)
			{
				FW_PRINTF("Target offset is not correctly aligned.\n");
				return;
			}
			
			// オフセットの調整. 
			switch(m_offsetOriginMethod)
			{
			case kOffsetAddress:
				{					
					labelPosition -= m_offsetPosition - (m_offsetPosition % m_offsetStride);
				}
				break;
			case kTopOfFile:
				break;
			default:
				{
					FW_PRINTF("Unknown offset origin method.\n");
					return;
				}
				break;
			}
			
			// オフセット値からストライド単位を求める. 
			int roundingStep = m_offsetStride - 1;
			if(labelPosition < 0)
			{
				roundingStep = 1 - m_offsetStride;
			}
			labelPosition	= (labelPosition + roundingStep);
			labelPosition	= labelPosition / static_cast<int>(m_offsetStride);
			
			// ストライド単位が正しいかどうかを確認. 
			unsigned int bitCount = 0;
			if(kOffsetAddress == m_offsetOriginMethod)
			{
				int64_t absolute = labelPosition;
				if(absolute<0)
				{
					absolute *= -1;
				}
				// 符号付きのオフセット.
				const int64_t one = 1;
				for(int bit=62; bit>-1; --bit)
				{
					if(absolute & (one<<bit))
					{
						bitCount = bit + 2;
						break;
					}
				}
			}
			else
			{
				// 符号無しのオフセット. 
				const int64_t one = 1;
				for(int bit=63; bit>-1; --bit)
				{
					if(labelPosition & (one<<bit))
					{
						bitCount = bit + 1;
						break;
					}
				}
			}
			if(bitCount > m_offsetByteSize * 8)
			{
				FW_PRINTF("Offset is too large to fit in given storage.\n");
				return;
			}
			
			// 必要であればスワップする.
			if(ShouldReverse(endianess))
			{
				labelPosition = SwapEndianess(labelPosition);
				out.seekp(m_offsetPosition);
				out.write(reinterpret_cast<const char*>(&labelPosition) + (sizeof(labelPosition) - m_offsetByteSize),
						  m_offsetByteSize);
			}
			else
			{
				out.seekp(m_offsetPosition);
				out.write(reinterpret_cast<const char*>(&labelPosition), m_offsetByteSize);
			}
		}
		
		void				SetLabel(BinaryWriter::Label targetLabel)
		{
			m_label = targetLabel;
		}
		
	private:
		BinaryWriter::Label			m_label;
		mutable size_t				m_offsetPosition;
		OffsetOrigin				m_offsetOriginMethod;
		unsigned int				m_offsetByteSize;
		unsigned int				m_offsetStride;
	}; // class OffsetNode 
	
	//-------------------------------------------------------------------------
	// binary data interface 
	//-------------------------------------------------------------------------
	
	BinaryWriter::BinaryWriter()
	{
		m_impl = new BinaryWriterImpl();
	}

	BinaryWriter::~BinaryWriter()
	{
		FW_ASSERT(m_impl);
		delete m_impl;
	}
	
	size_t BinaryWriter::GetDataSize(size_t offset) const
	{
		FW_ASSERT(m_impl);
		return m_impl->GetDataSize(offset);
	}
	
	void BinaryWriter::WriteData(std::ostream& out, Endianess endianess) const
	{
		FW_ASSERT(m_impl);
		return m_impl->WriteData(out, endianess);
	}
	
	void BinaryWriter::WriteTag (std::ostream& out, Endianess endianess, OffsetOrigin origin) const
	{
		FW_ASSERT(m_impl);
		return m_impl->WriteTag(out, endianess, origin);
	}
	
} // namespace fw 
